#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <soc/gpio_struct.h>
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include <driver/ledc.h>
#include "st7789.h"


#define MADCTL_MY  0x80  ///< Bottom to top
#define MADCTL_MX  0x40  ///< Right to left
#define MADCTL_MV  0x20  ///< Reverse Mode
#define MADCTL_ML  0x10  ///< LCD refresh Bottom to top
#define MADCTL_RGB 0x00  ///< Red-Green-Blue pixel order
#define MADCTL_BGR 0x08  ///< Blue-Green-Red pixel order
#define MADCTL_MH  0x04  ///< LCD refresh right to left

#define ST7789_NOP			0x00
#define ST7789_SWRESET		0x01
#define ST7789_RDDID		0x04
#define ST7789_RDDST		0x09

#define ST7789_RDDPM		0x0A      // Read display power mode
#define ST7789_RDD_MADCTL	0x0B      // Read display MADCTL
#define ST7789_RDD_COLMOD	0x0C      // Read display pixel format
#define ST7789_RDDIM		0x0D      // Read display image mode
#define ST7789_RDDSM		0x0E      // Read display signal mode
#define ST7789_RDDSR		0x0F      // Read display self-diagnostic result (ST7789V)

#define ST7789_SLPIN		0x10
#define ST7789_SLPOUT		0x11
#define ST7789_PTLON		0x12
#define ST7789_NORON		0x13

#define ST7789_INVOFF		0x20
#define ST7789_INVON		0x21
#define ST7789_GAMSET		0x26      // Gamma set
#define ST7789_DISPOFF		0x28
#define ST7789_DISPON		0x29
#define ST7789_CASET		0x2A
#define ST7789_RASET		0x2B
#define ST7789_RAMWR		0x2C
#define ST7789_RGBSET		0x2D      // Color setting for 4096, 64K and 262K colors
#define ST7789_RAMRD		0x2E

#define ST7789_PTLAR		0x30
#define ST7789_VSCRDEF		0x33      // Vertical scrolling definition (ST7789V)
#define ST7789_TEOFF		0x34      // Tearing effect line off
#define ST7789_TEON			0x35      // Tearing effect line on
#define ST7789_MADCTL		0x36      // Memory data access control
#define ST7789_IDMOFF		0x38      // Idle mode off
#define ST7789_IDMON		0x39      // Idle mode on
#define ST7789_RAMWRC		0x3C      // Memory write continue (ST7789V)
#define ST7789_RAMRDC		0x3E      // Memory read continue (ST7789V)
#define ST7789_COLMOD		0x3A

#define ST7789_RAMCTRL		0xB0      // RAM control
#define ST7789_RGBCTRL		0xB1      // RGB control
#define ST7789_PORCTRL		0xB2      // Porch control
#define ST7789_FRCTRL1		0xB3      // Frame rate control
#define ST7789_PARCTRL		0xB5      // Partial mode control
#define ST7789_GCTRL		0xB7      // Gate control
#define ST7789_GTADJ		0xB8      // Gate on timing adjustment
#define ST7789_DGMEN		0xBA      // Digital gamma enable
#define ST7789_VCOMS		0xBB      // VCOMS setting
#define ST7789_LCMCTRL		0xC0      // LCM control
#define ST7789_IDSET		0xC1      // ID setting
#define ST7789_VDVVRHEN		0xC2      // VDV and VRH command enable
#define ST7789_VRHS			0xC3      // VRH set
#define ST7789_VDVSET		0xC4      // VDV setting
#define ST7789_VCMOFSET		0xC5      // VCOMS offset set
#define ST7789_FRCTR2		0xC6      // FR Control 2
#define ST7789_CABCCTRL		0xC7      // CABC control
#define ST7789_REGSEL1		0xC8      // Register value section 1
#define ST7789_REGSEL2		0xCA      // Register value section 2
#define ST7789_PWMFRSEL		0xCC      // PWM frequency selection
#define ST7789_PWCTRL1		0xD0      // Power control 1
#define ST7789_VAPVANEN		0xD2      // Enable VAP/VAN signal output
#define ST7789_CMD2EN		0xDF      // Command 2 enable
#define ST7789_PVGAMCTRL	0xE0      // Positive voltage gamma control
#define ST7789_NVGAMCTRL	0xE1      // Negative voltage gamma control
#define ST7789_DGMLUTR		0xE2      // Digital gamma look-up table for red
#define ST7789_DGMLUTB		0xE3      // Digital gamma look-up table for blue
#define ST7789_GATECTRL		0xE4      // Gate control
#define ST7789_SPI2EN		0xE7      // SPI2 enable
#define ST7789_PWCTRL2		0xE8      // Power control 2
#define ST7789_EQCTRL		0xE9      // Equalize time control
#define ST7789_PROMCTRL		0xEC      // Program control
#define ST7789_PROMEN		0xFA      // Program mode enable
#define ST7789_NVMSET		0xFC      // NVM setting
#define ST7789_PROMACT		0xFE      // Program action


// The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct.
typedef struct
{
    uint8_t cmd;
    uint8_t data[16];
    uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;

static uint16_t ScreenBuff[ST7789_TFTHEIGHT * ST7789_TFTWIDTH];
//static uint16_t *ScreenBuff;

static spi_device_handle_t _spi;
static uint8_t rotation = 0;
static uint16_t _width, _height;
static uint16_t _X_Start, _Y_Start;

DRAM_ATTR static const lcd_init_cmd_t ili_init_cmds[] =
{
    {ST7789_SLPOUT, {0}, 0},
    {0, {0}, 0x80},
    //{ST7789_MADCTL, {0x08}, 1},
    {ST7789_RAMCTRL, {0x00, 0xE0}, 2},
    {ST7789_COLMOD, {0x05}, 1},
    {ST7789_PORCTRL, {0x0C, 0x0C, 0x00, 0x33, 0x33}, 5},
    {ST7789_GCTRL, {0x35}, 1},
    {ST7789_VCOMS, {0x32}, 1},
    //{ST7789_LCMCTRL, {0x0C}, 1},
    {ST7789_VDVVRHEN, {0x01, 0xFF}, 2},
    {ST7789_VRHS, {0x15}, 1},
    {ST7789_VDVSET, {0x20}, 1},
    {ST7789_FRCTR2, {0x0F}, 1},
    {ST7789_PWCTRL1, {0xA4, 0xA1}, 2},
    {ST7789_PVGAMCTRL, {0xD0, 0x08, 0x0E, 0x09, 0x09, 0x05, 0x31, 0x33, 0x48, 0x17, 0x14, 0x15, 0x31, 0x34}, 14},
    {ST7789_NVGAMCTRL, {0xD0, 0x08, 0x0E, 0x09, 0x09, 0x15, 0x31, 0x33, 0x48, 0x17, 0x14, 0x15, 0x31, 0x34}, 14},
    {ST7789_INVON, {0}, 0},
    //{ST7789_CASET, {0x00, 0x00, 0x00, 0xEF}, 4},
    //{ST7789_RASET, {0x00, 0x00, 0x00, 0x17}, 4},
    {0, {0}, 0x80},
    {ST7789_DISPON, {0}, 0},
    {0, {0}, 0x80},
    {0, {0}, 0xff},
};


//==============================================================================
//Send a command to the LCD. Uses spi_device_transmit, which waits until the transfer is complete.
//==============================================================================
void lcd_cmd(const uint8_t cmd)
{
	esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length = 8;                     //Command is 8 bits
    t.tx_buffer = &cmd;               //The data is the cmd itself
    t.user = (void*) 0;                //D/C needs to be set to 0
    ret = spi_device_transmit(_spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}
//==============================================================================


//==============================================================================
//Send data to the LCD. Uses spi_device_transmit, which waits until the transfer is complete.
//==============================================================================
void lcd_data(const uint8_t *data, int len)
{
    if (len == 0)
    	return;             //no need to send anything

    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length = len * 8;                 //Len is in bytes, transaction length is in bits.
    t.tx_buffer = data;               //Data
    t.user = (void*) 1;                //D/C needs to be set to 1
    ret = spi_device_transmit(_spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}
//==============================================================================


//==============================================================================
// This function is called (in irq context!) just before a transmission starts. It will set the D/C line to the value indicated in the user field.
//==============================================================================
void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
{
    int dc = (int) t->user;
    gpio_set_level(PIN_DC, dc);
}
//==============================================================================

//==============================================================================
// 设置用于填充的屏幕边框的步骤
//==============================================================================
void st7789_setWindow(uint16_t x, uint16_t y, uint16_t x_end, uint16_t y_end)
{
    x += _X_Start;
    y += _Y_Start;

	uint8_t Buff[4];

    lcd_cmd(ST7789_CASET); // Column addr set
    Buff[0] = (x >> 8) & 0xFF;
    Buff[1] = x & 0xFF;
    Buff[2] = ((x + x_end - 1) >> 8) & 0xFF;
    Buff[3] = (x + x_end - 1) & 0xFF;
    lcd_data(Buff, 4);

    lcd_cmd(ST7789_RASET); // Row addr set
    Buff[0] = (y >> 8) & 0xFF;
    Buff[1] = y & 0xFF;
    Buff[2] = ((y + y_end - 1) >> 8) & 0xFF;
    Buff[3] = (y + y_end - 1) & 0xFF;
    lcd_data(Buff, 4);


    lcd_cmd(ST7789_RAMWR); // write to RAM
}
//==============================================================================


//==============================================================================
// 设置屏幕旋转
//==============================================================================
void st7789_setRotation(uint8_t m)
{
    rotation = m % 4; // can't be higher than 3
    switch (rotation)
    {
        case 0:
            m = (MADCTL_RGB);
            _width  = ST7789_TFTWIDTH;
            _height = ST7789_TFTHEIGHT;
            break;
        case 1:
            m = (MADCTL_MX | MADCTL_MV | MADCTL_RGB);
            _width  = ST7789_TFTHEIGHT;
            _height = ST7789_TFTWIDTH;
            break;
        case 2:
            m = (MADCTL_MX | MADCTL_MY | MADCTL_RGB);
            _width  = ST7789_TFTWIDTH;
            _height = ST7789_TFTHEIGHT;
            break;
        case 3:
            m = (MADCTL_MY | MADCTL_MV | MADCTL_RGB);
            _width  = ST7789_TFTHEIGHT;
            _height = ST7789_TFTWIDTH;
            break;
    }

    lcd_cmd(ST7789_MADCTL);
    lcd_data(&m, 1);
}
//==============================================================================

//==============================================================================
// 背光
//==============================================================================
void st7789_SetBL(uint8_t value)
{
#ifdef PIN_BL
    if (value == 0)
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, 255, 1);
    }
    if (value == 25)
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, 220, 500);
    }
    if (value == 50)
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, 213, 500);
    }
    if (value == 75)
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, 208, 500);
    }
    if (value == 100)
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, 0, 500);
    }

    /*
    if (value == 0)
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, 255, 1);
    }
    else
    {
        ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,
                                LEDC_CHANNEL_3, (255 - (value * 2)), 500);
    } */
    ledc_fade_start(LEDC_LOW_SPEED_MODE,
                    LEDC_CHANNEL_3, LEDC_FADE_NO_WAIT);
#endif
}
//==============================================================================


static void BrightnessOutInit(void)
{
    ledc_channel_config_t ledc_channel =
        {
            .gpio_num = PIN_BL,
            .speed_mode = LEDC_LOW_SPEED_MODE,
            .channel = LEDC_CHANNEL_3,
            .timer_sel = LEDC_TIMER_1,
            .duty = 0,
            .hpoint = 0,
        };
    ledc_timer_config_t ledc_timer = {
        .speed_mode = LEDC_LOW_SPEED_MODE,      // timer mode
        .duty_resolution = LEDC_TIMER_8_BIT, // resolution of PWM duty
        .timer_num = LEDC_TIMER_1,              // timer index
        .freq_hz = 5000,                        // frequency of PWM signal
        //.clk_cfg = LEDC_AUTO_CLK,               // Auto select the source clock
    };
    // Set configuration of timer0 for high speed channels
    ledc_timer_config(&ledc_timer);
    ledc_channel_config(&ledc_channel);

    ledc_fade_func_install(0);

    ledc_set_fade_with_time(ledc_channel.speed_mode,
                            ledc_channel.channel, 0, 10);

    ledc_fade_start(ledc_channel.speed_mode,
                    ledc_channel.channel, LEDC_FADE_NO_WAIT);
}

//==============================================================================
// lcd初始化
//==============================================================================
static void lcd_init()
{
    int cmd = 0;
    const lcd_init_cmd_t* lcd_init_cmds;

    //Initialize non-SPI GPIOs
    gpio_set_direction(PIN_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_RST, GPIO_MODE_OUTPUT);
#ifdef PIN_BL
    BrightnessOutInit();
#endif

    //Reset the display
    gpio_set_level(PIN_RST, 0);
    vTaskDelay(100 / portTICK_RATE_MS);
    gpio_set_level(PIN_RST, 1);
    vTaskDelay(100 / portTICK_RATE_MS);

    //printf("LCD ST7789 initialization.\n");
    lcd_init_cmds = ili_init_cmds;

    //Send all the commands
    while (lcd_init_cmds[cmd].databytes != 0xff)
    {
        lcd_cmd(lcd_init_cmds[cmd].cmd);
        lcd_data(lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes & 0x1F);

        if (lcd_init_cmds[cmd].databytes & 0x80)
            vTaskDelay(100 / portTICK_RATE_MS);

        cmd++;
    }

    
    //st7789_SetBL(100);
    st7789_SetBL(0);
}
//==============================================================================

#define DISPLAY_SPI_HOST	VSPI_HOST	// VSPI_HOST
//==============================================================================
// 初始化
//==============================================================================
void st7789_init(uint16_t width, uint16_t height)
{
	_width = width;
	_height = height;
    _X_Start = ST7789_X_Start;
    _Y_Start = ST7789_Y_Start;

  spi_bus_config_t buscfg =
  {
    .miso_io_num = PIN_MISO,
    .mosi_io_num = PIN_MOSI,
    .sclk_io_num = PIN_CLK,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = ST7789_TFTHEIGHT * ST7789_TFTWIDTH * 2
  };

  spi_device_interface_config_t devcfg =
  {
    .clock_speed_hz = 40000000, //24 * 1000 * 1000,           //Clock out at 26 MHz
    .mode=0,                                //SPI mode 0
    .spics_io_num = PIN_CS,             //CS pin
    .queue_size = 7,                        //We want to be able to queue 7 transactions at a time
    .pre_cb = lcd_spi_pre_transfer_callback,//Specify pre-transfer callback to handle D/C line
  };

  esp_err_t ret;

  //Initialize the SPI bus
  ret = spi_bus_initialize(DISPLAY_SPI_HOST, &buscfg, 1);
  ESP_ERROR_CHECK(ret);
  //Attach the LCD to the SPI bus
  ret = spi_bus_add_device(DISPLAY_SPI_HOST, &devcfg, &_spi);
  ESP_ERROR_CHECK(ret);
  //Initialize the LCD
  lcd_init();

  st7789_setRotation(DIRECTION90);
}
//==============================================================================

//==============================================================================
// 关闭显示
//==============================================================================
void st7789_DisplayOff(void)
{
    lcd_cmd(ST7789_DISPOFF);
}
//==============================================================================

//==============================================================================
static void SwapBytes(uint16_t *color)
{
	uint8_t temp = *color >> 8;
	*color = (*color << 8) | temp;
}
//==============================================================================


#if (ST7789_MODE == ST7789_DIRECT_MODE)

//==============================================================================
void st7789_DrawPixel(int16_t x, int16_t y, uint16_t color)
{
	if ((x < 0) ||(x >= _width) || (y < 0) || (y >= _height))
		return;

	SwapBytes(&color);

	st7789_setWindow(x, y, x, y);
	lcd_data((uint8_t *) &color, 2);
}
//==============================================================================


//==============================================================================
void st7789_FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
	if ((w <= 0) || (h <= 0) || (x >= _width) || (y >= _height))
		return;

	if ((x + w) > _width)
		w = _width - x;
	if ((y + h) > _height)
		h = _height - y;

	SwapBytes(&color);

	uint16_t *Buff = &ScreenBuff[y * _width + x];
	for (uint32_t i = 0; i < h * w; i++)
		Buff[i] = color;

	st7789_setWindow(x, y, x + w - 1, y + h - 1);
	lcd_data((uint8_t *) Buff, w * h * 2);
}
//==============================================================================
#endif


#if (ST7789_MODE == ST7789_BUFFER_MODE)
//==============================================================================
void st7789_DrawPixel(int16_t x, int16_t y, uint16_t color)
{
	if ((x < 0) ||(x >= _width) || (y < 0) || (y >= _height))
		return;

	SwapBytes(&color);

	ScreenBuff[y * _width + x] = color;
}
//==============================================================================


//==============================================================================
uint16_t st7789_GetPixel(int16_t x, int16_t y)
{
	if ((x < 0) ||(x >= _width) || (y < 0) || (y >= _height))
		return 0;

	uint16_t color = ScreenBuff[y * _width + x];
	SwapBytes(&color);
	return color;
}
//==============================================================================


//==============================================================================
void st7789_FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
	if ((w <= 0) || (h <= 0) || (x >= _width) || (y >= _height))
		return;

	if ((x + w) > _width)
		w = _width - x;
	if ((y + h) > _height)
		h = _height - y;

	SwapBytes(&color);

	for (uint16_t row = 0; row < h; row++)
	{
		for (uint16_t col = 0; col < w; col++)
			ScreenBuff[(y + row) * _width + x + col] = color;
	}
}
//==============================================================================

//==============================================================================
void st7789_update(void)
{
	st7789_setWindow(0, 0, ST7789_TFTHEIGHT, ST7789_TFTWIDTH);
	lcd_data((uint8_t *) ScreenBuff, ST7789_TFTHEIGHT * ST7789_TFTWIDTH * 2);
}
//==============================================================================

//==============================================================================
void st7789_getScreenData(uint16_t *pBuff)
{
	for (uint32_t pixel = 0; pixel < ST7789_TFTHEIGHT * ST7789_TFTWIDTH; pixel++, pBuff++)
		*pBuff = (ScreenBuff[pixel] >> 8) | (ScreenBuff[pixel] << 8);
}
//==============================================================================

#endif
